use heck::ToSnakeCase;
use moss_core::error::Error;
use moss_core::from_derive_input::FromDeriveInput;
use moss_core::util::type_add_colon2;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::Result;
use syn::{Attribute, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Ident, LitStr, Meta};

use super::syn_parse::named_fields_asign_values_token;

pub struct FromMetaImpl<'a> {
    pub input: &'a DeriveInput,
}

pub struct Flag;

#[derive(Default)]
pub struct TypeAttr {
    pub name: Option<String>,
    pub strip: Option<Flag>,
}

pub struct FieldAttr {
    // 重命名
    pub name: Option<String>,
}

impl FieldAttr {
    pub fn get_name(&self, ident: Option<&Ident>) -> Result<String> {
        match ident {
            Some(ident) => {
                if let Some(name) = &self.name {
                    return Ok(name.clone());
                }
                Ok(format!("{ident}").to_snake_case())
            }
            None => Err(Error::unsupported_shape("name").into()),
        }
    }
}

impl TypeAttr {
    pub fn get_name(&self, ident: &Ident) -> Option<String> {
        if self.strip.is_some() {
            return None;
        }
        if let Some(name) = &self.name {
            return Some(name.clone());
        }
        Some(format!("{ident}").to_snake_case())
    }
}

impl TryFrom<&Vec<Attribute>> for TypeAttr {
    type Error = syn::Error;

    fn try_from(attrs: &Vec<Attribute>) -> std::result::Result<Self, Self::Error> {
        let mut name = None;
        let mut strip = None;
        for attr in attrs {
            match &attr.meta {
                Meta::Path(path) => {
                    if path.is_ident("strip") {
                        if strip.is_some() {
                            return Err(moss_core::error::Error::duplicate_field("strip").into());
                        }
                        strip = Some(Flag);
                    } else {
                        let ident_name =
                            path.get_ident().map(|i| i.to_string()).unwrap_or_default();
                        return Err(moss_core::error::Error::unknown_field(&ident_name).into());
                    }
                }
                Meta::List(meta_list) => {
                    if meta_list.path.is_ident("name") {
                        if name.is_some() {
                            return Err(moss_core::error::Error::duplicate_field("name").into());
                        }
                        let lit: LitStr = meta_list.parse_args()?;
                        name = Some(lit.value());
                    } else {
                        let ident_name = meta_list
                            .path
                            .get_ident()
                            .map(|i| i.to_string())
                            .unwrap_or_default();
                        return Err(moss_core::error::Error::unknown_field(&ident_name).into());
                    }
                }
                Meta::NameValue(_) => {
                    let shape = attr.to_token_stream().to_string();
                    return Err(moss_core::error::Error::unsupported_shape(&shape).into());
                }
            }
        }
        Ok(Self { name, strip })
    }
}

impl TryFrom<&Vec<Attribute>> for FieldAttr {
    type Error = syn::Error;

    fn try_from(attrs: &Vec<Attribute>) -> std::result::Result<Self, Self::Error> {
        let mut name = None;
        for attr in attrs {
            match &attr.meta {
                Meta::List(meta_list) => {
                    if meta_list.path.is_ident("name") {
                        if name.is_some() {
                            return Err(moss_core::error::Error::duplicate_field("name").into());
                        }
                        let lit: LitStr = meta_list.parse_args()?;
                        name = Some(lit.value());
                    } else {
                        let ident_name = meta_list
                            .path
                            .get_ident()
                            .map(|i| i.to_string())
                            .unwrap_or_default();
                        return Err(moss_core::error::Error::unknown_field(&ident_name).into());
                    }
                }
                _ => {
                    let shape = attr.to_token_stream().to_string();
                    return Err(moss_core::error::Error::unsupported_shape(&shape).into());
                }
            }
        }
        Ok(Self { name })
    }
}

pub enum SnippetPart {
    FromPath(TokenStream),
    FromNv(TokenStream),
    FromList(TokenStream),
    NewType(TokenStream),
}

impl FromMetaImpl<'_> {
    fn visit_fields(
        name: Option<&String>,
        fields: &Fields,
        variant_ident: Option<&Ident>,
    ) -> Result<SnippetPart> {
        let variant_token = match variant_ident {
            Some(ident) => quote!(:: #ident),
            None => quote!(),
        };
        match fields {
            Fields::Named(fields_named) => {
                if let Some(name) = name {
                    let generated = Self::from_list_named_impl(name, &variant_token, fields_named)?;
                    Ok(SnippetPart::FromList(generated))
                } else if fields_named.named.len() == 1 {
                    let field = &fields_named.named[0];
                    let generated = Self::from_nv_impl(field, &variant_token)?;
                    Ok(SnippetPart::FromNv(generated))
                } else {
                    Err(Error::unsupported_format("multi name value").into())
                }
            }
            Fields::Unnamed(fields_unnamed) => {
                if let Some(name) = name {
                    let generated =
                        Self::from_list_unnamed_impl(name, &variant_token, fields_unnamed)?;
                    Ok(SnippetPart::FromList(generated))
                } else if fields_unnamed.unnamed.len() == 1 {
                    let inner_ty = &fields_unnamed.unnamed[0].ty;
                    let generated = quote! {
                        let value = #inner_ty::from_meta(item);
                        if let Ok(value) = value {
                            return Ok(Self #variant_token(value));
                        }
                    };
                    Ok(SnippetPart::NewType(generated))
                } else {
                    Err(Error::unsupported_format("miss name").into())
                }
            }
            Fields::Unit => {
                if let Some(name) = name {
                    let generated = quote! {
                        if path.is_ident(#name) {
                            return Ok(Self #variant_token);
                        }
                    };
                    Ok(SnippetPart::FromPath(generated))
                } else {
                    Err(Error::unsupported_format("none name").into())
                }
            }
        }
    }

    fn from_list_named_impl(
        name: &String,
        variant_token: &TokenStream,
        fields: &FieldsNamed,
    ) -> Result<TokenStream> {
        let generated = named_fields_asign_values_token(variant_token, fields)?;
        let generated = quote! {
            if list.path.is_ident(#name) {
                let result = list.parse_args_with(|stream: syn::parse::ParseStream| -> syn::Result<Self> {
                    #generated
                });
                if result.is_ok() {
                    return result
                }
            }
        };
        Ok(generated)
    }

    fn from_nv_impl(field: &syn::Field, variant_token: &TokenStream) -> Result<TokenStream> {
        let moss_attr = FieldAttr::try_from(&field.attrs)?;
        let ident = field.ident.as_ref();
        let name = moss_attr.get_name(ident)?;
        let mut ty = field.ty.clone();
        type_add_colon2(&mut ty);
        let generated = quote! {
            let path = &name_value.path;
            if path.is_ident(#name) {
                let value = &name_value.value;
                let value = #ty::from_expr(value)?;
                return Ok(Self #variant_token { #ident: value });
            }
        };
        Ok(generated)
    }

    fn from_list_unnamed_impl(
        name: &String,
        variant_token: &TokenStream,
        fields: &FieldsUnnamed,
    ) -> Result<TokenStream> {
        let mut define_idents_token = quote!();
        let mut asign_values_token = quote!();
        let field_count = fields.unnamed.len();
        let max_index = field_count - 1;
        for (index, _field) in (&fields.unnamed).into_iter().enumerate() {
            let ty = &_field.ty;
            let define_ident = format_ident!("_unnamed_{index}");
            if index < max_index {
                define_idents_token = quote! {
                    #define_idents_token
                    let mut #define_ident = <#ty as moss::from_meta::UnnamedFieldParser>::parse(stream)?;
                    if !stream.is_empty() {
                        let _: syn::Token![,] = stream.parse()?;
                        if stream.is_empty() {
                            return Err(moss::error::Error::too_few_items(#field_count).into());
                        }
                    }
                };
            } else {
                define_idents_token = quote! {
                    #define_idents_token
                    let mut #define_ident = <#ty as moss::from_meta::UnnamedFieldParser>::parse(stream)?;
                    if !stream.is_empty() {
                        let _: syn::Token![,] = stream.parse()?;
                        if !stream.is_empty() {
                            return Err(moss::error::Error::too_many_items(#field_count).into());
                        }
                    }
                };
            }
            asign_values_token = quote! {
                #asign_values_token
                #define_ident,
            };
        }
        let generated = quote! {
            #define_idents_token

            let result = Self #variant_token (
                #asign_values_token
            );
            Ok(result)
        };
        let generated = quote! {
            if list.path.is_ident(#name) {
                let result = list.parse_args_with(|stream: syn::parse::ParseStream| -> syn::Result<Self> {
                    #generated
                });
                if result.is_ok() {
                    return result
                }
            }
        };
        Ok(generated)
    }
}

impl FromMetaImpl<'_> {
    pub fn gen_from_meta_token(self) -> Result<TokenStream> {
        let moss_attr = TypeAttr::try_from(&self.input.attrs)?;
        let name = moss_attr.get_name(&self.input.ident);
        let mut from_path_token = quote!();
        let mut from_nv_token = quote!();
        let mut from_list_outer_token = quote!();
        let mut from_list_inner_token = quote!();
        let mut from_meta_token = quote!();

        let mut snippet_concat = |nest: bool, snippet: SnippetPart| match snippet {
            SnippetPart::FromPath(snippet) => {
                if nest {
                    from_list_inner_token = quote! {
                        #from_list_inner_token
                        let result = list.parse_args_with(|stream: syn::parse::ParseStream| -> syn::Result<Self> {
                            let path: syn::Path = stream.parse()?;
                            #snippet
                            let shape = <syn::MetaList as quote::ToTokens>::to_token_stream(&list).to_string();
                            Err(moss::error::Error::unsupported_format(&shape).into())
                        });
                        if result.is_ok() {
                            return result;
                        }
                    };
                } else {
                    from_path_token = quote! {
                        #from_path_token
                        #snippet
                    };
                }
            }
            SnippetPart::FromNv(snippet) => {
                if nest {
                    from_list_inner_token = quote! {
                        #from_list_inner_token
                        let result = list.parse_args_with(|stream: syn::parse::ParseStream| -> syn::Result<Self> {
                            let name_value: syn::MetaNameValue = stream.parse()?;
                            #snippet
                            let shape = <syn::MetaList as quote::ToTokens>::to_token_stream(&list).to_string();
                            Err(moss::error::Error::unsupported_format(&shape).into())
                        });
                        if result.is_ok() {
                            return result;
                        }
                    };
                } else {
                    from_nv_token = quote! {
                        #from_nv_token
                        #snippet
                    };
                }
            }
            SnippetPart::FromList(snippet) => {
                if nest {
                    from_list_inner_token = quote! {
                        #from_list_inner_token
                        let result = list.parse_args_with(|stream: syn::parse::ParseStream| -> syn::Result<Self> {
                            let list: syn::MetaList = stream.parse()?;
                            #snippet
                            let shape = <syn::MetaList as quote::ToTokens>::to_token_stream(&list).to_string();
                            Err(moss::error::Error::unsupported_format(&shape).into())
                        });
                        if result.is_ok() {
                            return result;
                        }
                    };
                } else {
                    from_list_outer_token = quote! {
                        #from_list_outer_token
                        #snippet
                    };
                }
            }
            SnippetPart::NewType(snippet) => {
                from_meta_token = quote! {
                    #from_meta_token
                    #snippet
                };
            }
        };

        match &self.input.data {
            syn::Data::Struct(data_struct) => {
                let snippet = Self::visit_fields(name.as_ref(), &data_struct.fields, None)?;
                snippet_concat(false, snippet);
            }
            syn::Data::Enum(data_enum) => {
                for variant in &data_enum.variants {
                    let attr = TypeAttr::try_from(&variant.attrs)?;
                    let varaint_name = match attr.strip {
                        Some(_) => name.clone(),
                        None => attr.get_name(&variant.ident).or(name.clone()),
                    };
                    let nest = moss_attr.strip.is_none() && attr.strip.is_none();
                    let snippet = Self::visit_fields(
                        varaint_name.as_ref(),
                        &variant.fields,
                        Some(&variant.ident),
                    )?;
                    snippet_concat(nest, snippet);
                }
            }
            syn::Data::Union(_) => return Err(Error::unexpected_type("union").into()),
        }

        let mut from_list_token = from_list_outer_token;
        if !from_list_inner_token.is_empty() {
            from_list_token = quote! {
                #from_list_token
                if list.path.is_ident(#name) {
                    #from_list_inner_token
                    return result;
                }
            }
        }

        let generated = quote! {
            fn from_meta(item: &syn::Meta) -> syn::Result<Self> {
                #from_meta_token
                match item {
                    syn::Meta::Path(value) => Self::from_path(value),
                    syn::Meta::List(value) => Self::from_list(value),
                    syn::Meta::NameValue(value) => Self::from_name_value(value),
                }
            }

            fn from_path(path: &syn::Path) -> syn::Result<Self> {
                #from_path_token
                let shape = <syn::Path as quote::ToTokens>::to_token_stream(path).to_string();
                Err(moss::error::Error::unsupported_shape(&shape).into())
            }

            fn from_list(list: &syn::MetaList) -> syn::Result<Self> {
                #from_list_token
                let shape = <syn::MetaList as quote::ToTokens>::to_token_stream(list).to_string();
                Err(moss::error::Error::unsupported_shape(&shape).into())
            }

            fn from_name_value(name_value: &syn::MetaNameValue) -> syn::Result<Self> {
                #from_nv_token
                let shape = <syn::MetaNameValue as quote::ToTokens>::to_token_stream(name_value).to_string();
                Err(moss::error::Error::unsupported_shape(&shape).into())
            }
        };
        Ok(generated)
    }
}

impl<'a> FromDeriveInput<'a> for FromMetaImpl<'a> {
    fn from_derive_input(input: &'a DeriveInput) -> Result<Self> {
        Ok(Self { input })
    }
}

pub fn expand(input: &DeriveInput) -> Result<TokenStream> {
    let code_generator = FromMetaImpl::from_derive_input(input)?;
    let for_ident = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let from_meta_fn_token = code_generator.gen_from_meta_token()?;
    let generated = quote! {
        impl #impl_generics moss::from_meta::FromMeta for #for_ident #ty_generics #where_clause {
            #from_meta_fn_token
        }

        impl #impl_generics moss::from_meta::NamedFieldValueParser for #for_ident #ty_generics #where_clause {

            fn parse(meta: &syn::Meta) -> syn::Result<Self> {
                if let syn::Meta::List(meta_list) = meta {
                    let value = meta_list.parse_args_with(
                        |stream: syn::parse::ParseStream| -> syn::Result<Self> { stream.parse::<Self>() },
                    )?;
                    return Ok(value);
                }
                let name = quote::ToTokens::to_token_stream(&meta).to_string();
                Err(moss::error::Error::unsupported_format(&name).into())
            }
        }

        impl #impl_generics moss::from_meta::UnnamedFieldParser for #for_ident #ty_generics #where_clause {

            fn parse(stream: syn::parse::ParseStream) -> syn::Result<Self> {
                stream.parse()
            }
        }

        impl #impl_generics syn::parse::Parse for #for_ident #ty_generics #where_clause {
            fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
                let meta: syn::Meta = input.parse()?;
                let data = <Self as moss::from_meta::FromMeta>::from_meta(&meta)?;
                Ok(data)
            }
        }

        impl #impl_generics TryFrom<&syn::Attribute> for #for_ident #ty_generics #where_clause {
            type Error = syn::Error;

            fn try_from(value: &syn::Attribute) -> syn::Result<Self> {
                let meta = &value.meta;
                let data = <Self as moss::from_meta::FromMeta>::from_meta(meta)?;
                Ok(data)
            }
        }
    };
    Ok(generated)
}
